Logical Operators
If you've been working with JavaScript for a while, you're probably already familiar with the AND operator (&&
) and the OR operator (||
):
const isLoggedIn = true;const userRole = 'administrator';
if (isLoggedIn && userRole === 'administrator') { // This code only runs if BOTH conditions above are truthy}
There's something that many JavaScript developers don't realize about these operators, however: they also act as control flow operators.
result
?const myAge = 35;const result = myAge < 50 && myAge;
It is totally reasonable to expect that the answer would be true
:
myAge < 50
resolves totrue
myAge
resolves to35
, which is a truthy value
If both sides are truthy, the whole thing should evaluate to true
, right?
Afraid not! The &&
operator isn't designed to produce a boolean value. It's designed to resolve to one of the expressions on either side of the operator.
You can think of &&
like a gate. We're allowed to move through the gate if the left-hand side is truthy.
This becomes clearer when we chain multiple &&
s together:
const numOfChildren = 4;const parkingHasBeenValidated = true;const signatureOnWaiver = '';
const admissionTicket = numOfChildren && parkingHasBeenValidated && signatureOnWaiver && generateTicket();
The first expression in this chain is numOfChildren
, which is 4
. This is a truthy value, and so the first &&
gate is unlocked.
The second expression is parkingHasBeenValidated
, which is true
. We move through the second gate.
The third expression, signatureOnWaiver
, is an empty string (''
), which is falsy. This means that the third &&
gate stays locked. We can't go any further. The current expression, ''
, is what gets resolved.
As a result, admissionTicket
would be assigned ''
. Not false
.
Let's suppose signatureOnWaiver
wasn't an empty string:
const numOfChildren = 4;const parkingHasBeenValidated = true;const signatureOnWaiver = 'Becky Chambers';
const admissionTicket = numOfChildren && parkingHasBeenValidated && signatureOnWaiver && generateTicket();
Now, when we get to the third expression, we get a truthy value (the string "Becky Chambers"
). The final gate is unlocked.
Because all of the gates are unlocked, the final expression will be passed along. admissionTicket
will be equal to whatever the generateTicket
function returns.
Here's how we'd set up exactly the same code, but using if/else instead of logical operators:
let admissionTicket;
if (!numOfChildren) { admissionTicket = numOfChildren;} else if (!parkingHasBeenValidated) { admissionTicket = parkingHasBeenValidated;} else if (!signatureOnWaiver) { admissionTicket = signatureOnWaiver;} else { admissionTicket = generateTicket();}
admissionTicket
will be assigned to the first falsy expression. If we make it through all of the gates, admissionTicket
will be assigned to the final expression, whether it's truthy or falsy.
There's one more interesting takeaway here: The generateTicket
function is only called if we make it through all the gates. In the first example, when signatureOnWaiver
was falsy, the generateTicket
function was never called.
This is because of short-circuiting. If one of the expressions before an &&
operator evaluates to a falsy value, the rest of the expressions are skipped. They won't be executed at all.
Here's a less-complex example:
false && console.log('I will never run');
Because false
is a falsy value, the &&
gate remains locked, and the console.log
never fires. It's the same as if the console.log was within an if
statement:
if (false) { console.log('I will never run');}
The OR operator
The ||
operator is exactly like the &&
operator, with one key difference: ||
doesn't stop and provide the first falsy value. It stops and provides the first truthy value.
For example:
const userImageSrc = null;const teamImageSrc = null;const defaultImageSrc = '/images/cat.jpg';
const src = userImageSrc || teamImageSrc || defaultImageSrc;
We check the first expression, userImageSrc
, which is null
. Because it's falsy, the ||
gate is unlocked, and we proceed onwards.
The second expression, teamImageSrc
, is also null
, and so the second ||
gate unlocks.
As with &&
, when we make it to the final item, the final item is provided regardless of whether it's truthy or falsy. And so src
will be assigned to defaultImageSrc
.
However, in this case:
const userImageSrc = '/images/my-avatar.png';const teamImageSrc = null;const defaultImageSrc = '/images/cat.jpg';
const src = userImageSrc || teamImageSrc || defaultImageSrc;
The first item, userImageSrc
, is truthy! Because it's truthy, the first ||
gate remains locked, and src
is assigned to userImageSrc
.
Here's how we'd represent this code using if/else:
let src;
if (userImageSrc) { src = userImageSrc;} else if (teamImageSrc) { src = teamImageSrc;} else { src = defaultImageSrc;}